#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016-2018, Niklas Hauser
# Copyright (c) 2017, Fabian Greif
# Copyright (c) 2021, Raphael Lehmann
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

props = {}

descr_example = """
```cpp
using namespace modm::platform;
// Using only hardware buffers for Tx and Rx (both 1 symbol)
using UartN = BufferedUart<U(s)artHalN>;
// using only TX software buffer
using UartN = BufferedUart<U(s)artHalN, UartTxBuffer<512>>;
// using only RX software buffer
using UartN = BufferedUart<U(s)artHalN, UartRxBuffer<256>>;
// using both TX and RX software buffers
using UartN = BufferedUart<U(s)artHalN, UartTxBuffer<512>, UartRxBuffer<256>>;
```
"""

descr_buffer = """
The buffer configuration is now implemented in C++.
Please replace any `modm::platform::U(s)artN` type with:
""" + descr_example

class Instance(Module):
    def __init__(self, driver, instance):
        self.driver = driver
        self.instance = int(instance)

    def init(self, module):
        if self.driver["name"] == "lpuart":
            module.name = "lpuart" + str(self.instance)
        else:
            module.name = str(self.instance)
        module.description = "Instance {}".format(self.instance)

    def prepare(self, module, options):
        module.add_alias(Alias(name="buffer.tx", description=descr_buffer))
        module.add_alias(Alias(name="buffer.rx", description=descr_buffer))
        module.depends(":platform:uart")
        return True

    def build(self, env):
        device = env[":target"].identifier
        global props
        props["id"] = self.instance
        props["driver"] = self.driver
        props["features"] = self.driver["feature"] if "feature" in self.driver else []
        props["uart_type"] = self.driver["name"].capitalize()
        props["name"] = self.driver["name"].capitalize() + str(self.instance)
        props["hal"] = self.driver["name"].capitalize() + "Hal" + str(self.instance)

        env.substitutions = props
        env.outbasepath = "modm/src/modm/platform/uart"

        if self.driver["name"] == "lpuart":
            uart = "lpuart"
        else:
            uart = "uart"

        env.template("uart_hal.hpp.in", "{}_hal_{}.hpp".format(uart, self.instance))
        env.template("uart_hal_impl.hpp.in", "{}_hal_{}_impl.hpp".format(uart, self.instance))
        env.template("uart.cpp.in", "{}_{}.cpp".format(uart, self.instance))
        props["instances"].append(props["name"])

def init(module):
    module.name = ":platform:uart"
    module.description = """
# Universal Asynchronous Receiver Transmitter (UART)

The UART buffer configuration is implemented as C++ template arguments:
""" + descr_example + """

## Using FreeRTOS buffers

A special buffer implementation is available for FreeRTOS that uses the proper
queue implementation to sleep and wake up the calling thread:

```cpp
// using both TX and RX software buffers
using UartTxRxN = BufferedUart<U(s)artHalN, UartTxBufferFreeRtos<512>, UartRxBufferFreeRtos<256>>;
```
"""

def prepare(module, options):
    device = options[":target"]
    if not (device.has_driver("uart:stm32*")
            or device.has_driver("usart:stm32*")
            or device.has_driver("lpuart:stm32*")):
        return False

    module.depends(
        ":architecture:atomic",
        ":architecture:interrupt",
        ":architecture:register",
        ":architecture:uart",
        ":math:algorithm",
        ":cmsis:device",
        ":platform:gpio",
        ":platform:rcc",
        ":utils")

    global props
    drivers = (device.get_all_drivers("uart") + device.get_all_drivers("usart") + device.get_all_drivers("lpuart"))
    props["extended_driver"] = ("extended" in drivers[0]["type"])
    props["over8"] = ("feature" in drivers[0]) and ("over8" in drivers[0]["feature"])
    props["tcbgt"] = ("feature" in drivers[0]) and ("tcbgt" in drivers[0]["feature"])
    props["instances"] = []

    for driver in drivers:
        for instance in driver["instance"]:
            module.add_submodule(Instance(driver, instance))

    shared_irqs_new = dict()
    shared_irqs = [v["name"] for v in device.get_driver("core")["vector"]]
    shared_irqs = [v for v in shared_irqs if ("USART" in v or "UART" in v or "LPUART" in v) and "_" in v]
    for shared_irq in shared_irqs:
        uart_type = ""
        import re
        for s in re.findall(r"L?P?US?ART[\d_]+", shared_irq):
            if "LPUART" in s:
                uart_type = "Lpuart"
            elif "UART" in s:
                uart_type = "Uart"
            elif "USART" in s:
                uart_type = "Usart"
            else:
                # use type from previous irq id
                pass
            irq_ids = re.findall(r"\d+", s)
            if len(irq_ids) == 2:
                irq_ids = range(int(irq_ids[0]), int(irq_ids[1])+1)
            for irq_id in irq_ids:
                shared_irqs_new[uart_type + str(irq_id)] = shared_irq
    props["shared_irqs"] = shared_irqs_new

    props["target"] = device.identifier

    return True

def build(env):
    device = env[":target"]

    global props
    env.substitutions = props
    env.outbasepath = "modm/src/modm/platform/uart"
    env.template("uart_base.hpp.in")

    props["uart_shared_irqs_data"] = dict()
    for name in props["instances"]:
        if name in props["shared_irqs"].keys():
            irq = props["shared_irqs"][name]
            if irq not in props["uart_shared_irqs_data"]:
                props["uart_shared_irqs_data"][irq] = list()
            props["uart_shared_irqs_data"][irq].append({
                "name": name,
                "id": name[-1],
                "type": name[:-1],
            })
    if len(props["uart_shared_irqs_data"]):
        env.template("uart_shared.cpp.in")

    env.copy("uart.hpp")
    env.copy("uart_buffer.hpp")
    if env.has_module(":freertos"):
        env.copy(repopath("ext/aws/uart_buffer_freertos.hpp"), "uart_buffer_freertos.hpp")

